#include "util.h"
#include "fpnumber.h"
#include <fstream>
#include <sstream>
#include <string>
#include <cassert>
#include <map>
#include <cmath>

#define HELP "uso: " << argv[0] << " -i <archivo_test>=conf.txt -o <archivo_salida>=stdout -v" << endl

using namespace std;

char locate_option(char** options, char options_size, char* str);
static bool verbose= 0;

FPNumber calcTaylor(ostream& o, FPNumber a, int n, bool neg) {
	char p=a.prec();
	FPNumber r(0.0,p);
	FPNumber i(0.0,p),ifact(1.0,p);
	for(int j=0;j<=n;j++) {
		if (neg & j) r-=(a^i)/ifact;
		else r+=(a^i)/ifact;
		ifact*=(i+=FPNumber(1.0,p));
	}
	return neg?r:FPNumber(1.0,p)/r;
}

FPNumber calcTaylorBack(ostream& o, FPNumber a, int n, 
				bool neg, bool horn) {
	char p=a.prec();
	FPNumber r(0.0,p);
	FPNumber i[n],ifact[n];
	i[0]=FPNumber(0.0,p);
	ifact[0]=FPNumber(1.0,p);
	for(int j=1;j<=n;j++) 
		ifact[j]=ifact[j-1]*(i[j]=(i[j-1]+FPNumber(1.0,p)));
	if(verbose) 
		o << "ifact = " << ifact << endl; 
	
	for(int j=n;j>=0;j--) {
		if (horn) r*=a;
		FPNumber t(horn?FPNumber(1.0,p):a^i[j]);
 		if (neg & j) r-=t/ifact[j]; 
		else r+=t/ifact[j];
	}
	FPNumber res=neg?r:FPNumber(1.0,p)/r;
	if(verbose) {
		o <<  "***************************" << endl;
		o << "\tresultado aproximado=" << r << endl << '\t';
		printDoubleBits(o, r.asDouble());
		o << "\tresultado copiado=" << res << endl << '\t';
		printDoubleBits(o, res.asDouble());
	}
	return res; 
}

enum Mode {
	Forward,
	Back,
	Horn
};

FPNumber calcFunc(ostream& o, FPNumber a, int n, bool neg, Mode m) {
	switch(m) {
		case Forward: return calcTaylor(o, a,n,neg);
		case Back: return calcTaylorBack(o, a,n,neg,false);
		case Horn: return calcTaylorBack(o, a,n,neg,true);
	}
}

void proc(ostream& o, string s) {
	map<string,Mode> modes;
	modes["F"]=modes["FORWARD"]=Forward;
	modes["B"]=modes["BACK"]=Back;
	modes["H"]=modes["HORN"]=Horn;
	istringstream in(s);
	double a; int p,neg,fn; string mode;
	in >> a >> p >> neg >> mode >> fn;
	for(int i=0;i<mode.size();++i) mode[i]=toupper(mode[i]);
	assert(modes.find(mode)!=modes.end());
	Mode m=modes[mode];
	assert((neg|1)==1);
	assert(0<p && p<=FPNumber::maxP);
	int tn=fn; in >> tn;
	assert(0<=fn && fn<=tn);
	o << "a=" << a << " p=" << p << " neg=" << neg 
		<< " mode=" << mode << " fn=" << fn << " tn=" << tn << endl;
	for(int n=fn;n<=tn;n++) {
		o.precision(10);
		FPNumber res= calcFunc(o, FPNumber(a,p),n,neg,m);
		double r = exp(-a);
		double error=abs((r-res.asDouble())/r);
		if(verbose) {
			o << "resultado \"exacto\"=" << r << endl;
			printDoubleBits(o, r);
		}
		o << error << ";" << res << ";" << r << (n==tn?"":" ");
	}
	o << endl;
}


int main(int argc, char* argv[]) {
	char* test_file= NULL;
	char* output_file= NULL;
 	char* options[]= {"-i", "-o", "-v"}; 
	char options_size= 3;
	enum OptionType {
		  INPUT_FILE= 0,
			OUTPUT_FILE= 1,
			VERBOSE= 2,
			OPTION_NOT_RECOGNIZED
	}; 
	for(int i= 1; i< argc; i++) {
		char option= locate_option(options, options_size, argv[i]);
		switch(option) {
		case INPUT_FILE:
			test_file= argv[i+1];
			break;
		case OUTPUT_FILE:
			output_file= argv[i+1];
			break;
		case VERBOSE:
			verbose= 1;
			break;
		}
	} 
	
	if(test_file == NULL) 
		test_file= "conf.txt";
	
	ifstream in(test_file);

	if(!in.good()) {
		cerr << "No se puede abrir el archivo " << argv[1] << endl;
		cerr << HELP;
		return 1;
	}

	string s;
	ostream* output_stream= &cout;
	ofstream tmp;
	if(output_file != NULL) {
		tmp.open(output_file);
 		output_stream= &tmp; 
	}
		
	while(getline(in,s)) {
		if (s==string(s.size(),' ') || s[0]=='#') continue;
			proc(*output_stream, s);
	}
	
	return 0;
}

char locate_option(char** options, char options_size, char* str) {
	for(int i= 0; i<options_size; i++) {
		if(strcmp(options[i], str) == 0)
			return i;
	}

	return options_size+1;
}
